﻿
package extremefx.modules.css {
	import extremefx.conditions.ensures;
	import extremefx.conditions.ensuresBool;
	import extremefx.events.EventArgs;
	import extremefx.extensions.isNullOrEmpty;
	import extremefx.storage.DisplayObjectLoader;
	import extremefx.storage.LoadQeue;
	import extremefx.storage.TextLoader;
	import extremefx.tools.ObjectUtils;
	import extremefx.tools.StringUtils;
	import extremefx.tools.getClassByName;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.utils.Dictionary;
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
			

	/**
	 * @author Marcelo Volmaro
	 */
	public class StyleSheet {
		private static const _propertyCache:Object = {};
		private static const _hypen:RegExp = /(-[a-z])/i;
		private static const _bitmaps:Array = [];

		private const _cachedStyles:Dictionary = new Dictionary();
		private const _styleList:Array = [];
		private const _styleIndexes:Array = [];
		
		private var _styles:Array;
		
		private var _onUnknownProperty:ICssParser;
		private var _loading:Boolean;
//		private var _qloader:LoadQeue;
//		private var _iloader:QeuedItem;
		private var _externalBitmaps:Array;
		private var _externals:uint;
		private var _loader:LoadQeue;

		public function StyleSheet(pCss:String = null) {
			if (!isNullOrEmpty(pCss)) parse(pCss);
		}
		
		public function setLoader(pLoader:LoadQeue):void {
			_loader = pLoader;
		}
		
		public function load(pUrl:String):void {
			ensuresBool(_loading).isFalse("This instance is already loading data.");
			ensures(_loader).isNotNull("A loader for this class has not been specified.");
			
			_loading = true;
			
			var iloader:TextLoader = TextLoader(_loader.addLoader(new TextLoader(pUrl)));
			
			iloader.onComplete.add(
				function(pSender:TextLoader, pArgs:EventArgs):void {
					parse(pSender.text);
				}
			);
			
			_loader.start();
		}
		
		public function getStyle(pSelector:String):Object {
			return _styleList[_styleIndexes[pSelector]].contents;
		}
		
		public function getComputedStyle(pNode:IDomNode, pPseudo:String = null):ComputedStyle {
			//create a hash for this node...
			//hash is parent[attName=attVal]:pseudo>node[attName...]:pseudo
			var names:Vector.<String> = new Vector.<String>, 
				tmp:String, attrs:Dictionary, tmpNode:IDomNode = pNode;
			
			do {
				tmp = tmpNode.nodeName;
				attrs = pNode.attributes;
				for (var attName:String in attrs){
					tmp += "["+attName+"="+attrs[attName]+"]";
				}
				
				if (!isNullOrEmpty(pPseudo)) tmp += ":"+pPseudo;
				names.push(tmp);
				
			} while (tmpNode = tmpNode.parentNode);
			
			names.reverse();
			tmp = names.join(" > ");
			
			if (_cachedStyles[tmp]) return _cachedStyles[tmp];
			
			var rules:Vector.<Selector> = _getMatchingRules(pNode, pPseudo);
			var out:Object = {};
			
			for each (var s:Selector in rules){
				ObjectUtils.merge(out, s.contents);
			}
			
			var cs:ComputedStyle = new ComputedStyle(tmp, out, this, pNode);
			_cachedStyles[tmp] = cs;
			
			return cs;
		}
		
		public static function getBitmapData(pBitmapUrl:String):BitmapData {
			return _bitmaps[pBitmapUrl];
		}
		
		public function setCustomParser(pIParser:ICssParser):void {
			_onUnknownProperty = pIParser;
		}
		
		public function listStyles():Array {
			return _styles.concat();
		}
		
		public function parse(pString:String):Boolean {
			_externalBitmaps = [];
			_styles = [];
			_externals = 0;
			
			for (var bd:String in _bitmaps){
				BitmapData(_bitmaps[bd]).dispose();
				_bitmaps[bd] = null;
			}
			
			// Compress the CSS...
			
			// remove comments
			pString = pString.replace(/\/\*[^*]*\*+([^\/][^*]*\*+)*\//sg, "");
			
			//remove linebreaks, tabs
			pString = pString.replace(/[\r|\n|\t]/g, "");

			//remove multiple spaces
			pString = pString.replace(/\s+/g," ");

			//remove additional un-needed spaces
			pString = pString.replace(/\s?({|}|;|,|:|\/)\s?/g, "$1");
			
			//there should be at least one block delimiter
			var parts:Array = pString.split("}");
			if (parts.length == 0) return false;
			var _temp:Array;
			
			for each (var part:String in parts) {
				_temp = part.split("{");
				
				var selectors:Array = _temp[0].split(",");
				
				if (selectors.length != 0) {
					var index:uint = 0, selector:String;
					
					while (selector = selectors[index++]) { 
						addSelector(selector, _temp[1]);
					}
				}
			}
			

			if (_externals){
				_loader.onQeueComplete.add(_bitmapsLoaded);
				_loader.start();
				
			} else {
				_bitmapsLoaded(null, null);
			}
			
			return true;
		}
		
		private function _bitmapLoaded(pSender:DisplayObjectLoader, pArgs:EventArgs):void {
			_bitmaps[pSender.userData["name"]] = Bitmap(pSender.displayObject).bitmapData;
		}
		
		private function _bitmapsLoaded(pSender:LoadQeue, pArgs:EventArgs):void {
			_externalBitmaps = null;
			_loading = false;
		}
		
		private function addSelector(pSelector:String, pBlock:String):void {
			var codes:Array = pBlock.split(";");
			if (codes.length == 0) return;

			_styles.push(pSelector);
			
			var _temp:Array;
			var style:Object = {};
			
			for each (var code:String in codes) {

				//skip non-valid or empty blocks
				if (code.indexOf(":") == -1) continue;
				
				_temp = code.split(":");
				var key:String = _temp[0];
				var value:String = _temp[1];

				if (_onUnknownProperty is ICssParser && _onUnknownProperty.canParse(key)){//check if custom parser...
					var ret:Object;
				
					if ((ret = _onUnknownProperty.parse(key, value))){ 
						for each(var pp:ParsedProperty in ret){
							_setValue(style, pp.name, pp.value, pp.image);
						}
						
						continue;
					}
				}
				
				/**
				 * @TODO: list-style
				 */
				 
				switch(key){
					case "line-height":
					case "top":
					case "left":
					case "bottom":
					case "right":
					case "width":
					case "min-width":
					case "max-width":
					case "height":
					case "min-height":
					case "max-height":
					case "z-index":
						_setValue(style, key, ParseCommons.parseLength(value));
						break;
						
					case "color":
						_setValue(style, key, ParseCommons.parseColor(value));
						break;
						
//					case "float":
//					case "clear":
//					case "display":
//					case "visibility":
//					case "position":
//					case "direction":
//					case "unicode-bidi":
//					case "text-align":
//					case "vertical-align":
//					case "anti-alias-type"://flash	[normal|advanced]
//					case "grid-fit-type"://flash	[none|pixel|subpixel]
//					case "embed-fonts"://flash		[embed|none]
//						_setValue(style, key, value);
//						break;
						
					case "text-shadow":
						var shadows:Array = value.split(",");
						var shads:Array = [];
						
						for each (var shadow:String in shadows){
							var storeOn:Object = {};
							var values:Array = shadow.split(" ");
							var offsetX:String, offsetY:String, blur:String, color:String, tmpVal:String;
							
							offsetX = offsetY = blur = color = null;
							
							for each (var shadowProp:String in values){
								tmpVal = ParseCommons.parseColor(shadowProp);
								if (tmpVal) {
									color = tmpVal;
									continue;
								}
								
								tmpVal = ParseCommons.parseLength(shadowProp);
								if (tmpVal){
									if (!offsetX) {
										offsetX = tmpVal;
										continue;
									}
									
									if (!offsetY) {
										offsetY = tmpVal;
										continue;
									}
									
									if (!blur) {
										blur = tmpVal;
									}
								}
							}
						
							_setValue(storeOn, "color", color);
							_setValue(storeOn, "top", offsetX);
							_setValue(storeOn, "left", offsetY);
							_setValue(storeOn, "blur", blur);
							shads.push(storeOn);
						}
						
						style["textShadow"] = shads;
						break;
					
					default:
						if (key.indexOf("border") == 0) {
							_parseBorder(style, key, value);
							
						} else if(key.indexOf("margin") == 0) {
							_parseMargin(style, key, value);
							
						} else if(key.indexOf("padding") == 0) {
							_parsePadding(style, key, value);
							
						} else if(key.indexOf("background") == 0) {
							_parseBackground(style, key, value);
							
						} else if(key.indexOf("font") == 0) {
							_parseFont(style, key, value);
							
						} else if(key == "text-indent" || key.indexOf("-spacing") != -1 || key == "white-space") {
							if (value != "normal") key = ParseCommons.parseLength(value);
							_setValue(style, key, value);
							
						} else {//Store as-is
							_setValue(style, key, value);
						}
				}
			}

			_styleIndexes[pSelector] = _styleList.push(new Selector(pSelector, style, _styleList.length)) - 1; 
			
		}
		
		private function _getMatchingRules(pNode:IDomNode, pPseudo:String = ""):Vector.<Selector> {
			var a:Vector.<Selector> = new Vector.<Selector>();
			for each (var s:Selector in _styleList){
				if (s.matches(pNode, pPseudo)) a.push(s);
			}
			
			a.sort(_comparer);
			return a;
		}
		
		private function _comparer(pA:Selector, pB:Selector):int {
			if (pA.specificity < pB.specificity) return -1;
			if (pB.specificity < pA.specificity) return 1;
			
			if (pA.order < pB.order) return -1;
			if (pB.order < pA.order) return 1;
			
			return 0;
		}
		
		private function _parseFont(pStyle:Object, pKey:String, pProperty:String):void {
			var val:String;
			
			if (pKey == "font"){
				var fstyle:String, fvariant:String, fweight:String;
					 
				fstyle = fvariant = fweight = "normal";
				var fSize:String="12px";
				var fFamily:String = "Tahoma";
				
				var ret:Array = pProperty.split(" ");
			
				for each (var i:String in ret) {
					if (i == "normal") continue;//this says nothing...
					
					if (ParseCommons.parseFontStyle(i)) fstyle = i;
					else if (ParseCommons.parseFontVariant(i)) fstyle = fvariant;
					else if (ParseCommons.parseFontWeight(i)) fweight = fweight;
					else if (i.indexOf("/") != -1){// size/line-height
						var tmp:Array = i.split("/");//[0]=size, [1]=line-height;
						fSize = tmp[0];
						_setValue(pStyle, "line-height", ParseCommons.parseLength(tmp[1]));
						
					} else fFamily = i;
				}
				
				_parseFont(pStyle, "font-style", fstyle);
				_parseFont(pStyle, "font-variant", fvariant);
				_parseFont(pStyle, "font-weight", fweight);
				_parseFont(pStyle, "font-size", fSize);
				_parseFont(pStyle, "font-family", fFamily);
				
			} else {
				switch (pKey){
					case "font-family":
						val = ParseCommons.parseQuotedText(pProperty);
						break;
						
					case "font-style":
						val = ParseCommons.parseFontStyle(pProperty);
						break;
						
					case "font-variant":
						val = ParseCommons.parseFontVariant(pProperty);
						break;
						
					case "font-weight":
						val = ParseCommons.parseFontWeight(pProperty);
						break;
					
					case "font-size":
						val = ParseCommons.parseFontSize(pProperty);
				}
				
				_setValue(pStyle, pKey, val);
			}
		}
		
		private function _parseBackground(pStyle:Object, pKey:String, pProperty:String):void {
			if (pKey == "background") {
				var bposx:String, bposy:String;
				var ret:Array = pProperty.split(" ");
				var val:String;
				
				for each (var i:String in ret) {
					if (ParseCommons.parseAttachment(i)) _parseBackground(pStyle, "background-attachment", i);
					else if (ParseCommons.parseRepeat(i)) _parseBackground(pStyle, "background-repeat", i);
					else if (ParseCommons.parseUrl(i)) _parseBackground(pStyle, "background-image", i);
					else if (ParseCommons.parseColor(i)) _parseBackground(pStyle, "background-color", i);
					else {
						val = ParseCommons.parseDirection(i);
						if (val) {
							if (!bposx) {
								bposx = val;
								
							} else {
								bposy = val;
							}
						}
					}
				}
				
				if (bposy) _parseBackground(pStyle, "background-position", bposy == "" ? bposx : bposx + " " + bposy);
				
			} else {
				switch (pKey) {
					case "background-color":
					case "background-repeat":
					case "background-attachment":
						_setValue(pStyle, pKey, pProperty);
						break;
						
					case "background-image":
						_setValue(pStyle, pKey, ParseCommons.parseUrl(pProperty), true);
						break;

					case "background-position":
						if (pProperty.indexOf(" ") > 1) {
							ret = pProperty.split(" ");
							_setValue(pStyle, pKey+"-x", ret[0]);
							_setValue(pStyle, pKey+"-y", ret[1]);
							
						} else {
							_setValue(pStyle, pKey+"-x", "50%");
							_setValue(pStyle, pKey+"-y", pProperty);
						}
						break;
						
					//unknown background property, store
					default:
						_setValue(pStyle, pKey, pProperty);
				}
			}
		}
		
		private function _parsePadding(pStyle:Object, pKey:String, pProperty:String):void {
			var dp:DirProp = ParseCommons.parseDirProp(pKey);
			
			if (dp.dir){
				_setValue(pStyle, dp.name, ParseCommons.parseLength(pProperty)); 
				
			} else if (pKey == "padding") {
				var ret:Vector.<String> = ParseCommons.parseFour(pProperty);
				_parsePadding(pStyle, "padding-top", ret[0]);
				_parsePadding(pStyle, "padding-right", ret[1]);
				_parsePadding(pStyle, "padding-bottom", ret[2]);
				_parsePadding(pStyle, "padding-left", ret[3]);
			}
		}
		
		private function _parseMargin(pStyle:Object, pKey:String, pProperty:String):void {
			var dp:DirProp = ParseCommons.parseDirProp(pKey);
			
			if (dp.dir){
				_setValue(pStyle, dp.name, ParseCommons.parseLength(pProperty));
				
			} else if (pKey == "margin") {
				var ret:Vector.<String> = ParseCommons.parseFour(pProperty);
				_parseMargin(pStyle, "margin-top", ret[0]);
				_parseMargin(pStyle, "margin-right", ret[1]);
				_parseMargin(pStyle, "margin-bottom", ret[2]);
				_parseMargin(pStyle, "margin-left", ret[3]);
			}
		}
		
		private function _parseBorder(pStyle:Object, pKey:String, pProperty:String):void {
			var ret:Vector.<String>;
			var dp:DirProp = ParseCommons.parseDirProp(pKey);
			
			if (dp.dir){
				//border-top || border-left || border-right || border-bottom
				//border-width || border-style || border-top-color
				var val:String;
				
				if (dp.prop){
					switch (dp.prop){
						case "width":
							val = ParseCommons.parseBorderWidth(pProperty);
							break;
							
						case "style":
							val = ParseCommons.parseBorderStyle(pProperty);
							break;
							
						case "color":
							val = pProperty;
					}
					
					_setValue(pStyle, dp.name, val);

				} else {
					ret = Vector.<String>(pProperty.split(" "));
					var width:String, style:String, color:String;
					
					for each(var i:String in ret){
						if (ParseCommons.parseBorderWidth(i)) width = i;
						if (ParseCommons.parseBorderStyle(i)) style = i;
						if (ParseCommons.parseColor(pProperty)) color = i; 
					}
					
					_parseBorder(pStyle, "border-" + dp.dir + "-width", width);
					_parseBorder(pStyle, "border-" + dp.dir + "-style", style);
					_parseBorder(pStyle, "border-" + dp.dir + "-color", color);
				}

			} else {
				switch(pKey){
					case "border-width":
						ret = ParseCommons.parseFour(pProperty);
						_parseBorder(pStyle, "border-top-width", ret[0]);
						_parseBorder(pStyle, "border-right-width", ret[1]);
						_parseBorder(pStyle, "border-bottom-width", ret[2]);
						_parseBorder(pStyle, "border-left-width", ret[3]);
						break;
						
					case "border-style":
						ret = ParseCommons.parseFour(pProperty);
						_parseBorder(pStyle, "border-top-style", ret[0]);
						_parseBorder(pStyle, "border-right-style", ret[1]);
						_parseBorder(pStyle, "border-bottom-style", ret[2]);
						_parseBorder(pStyle, "border-left-style", ret[3]);
						break;
						
					case "border-color":
						ret = ParseCommons.parseFour(pProperty);
						_parseBorder(pStyle, "border-top-color", ret[0]);
						_parseBorder(pStyle, "border-right-color", ret[1]);
						_parseBorder(pStyle, "border-bottom-color", ret[2]);
						_parseBorder(pStyle, "border-left-color", ret[3]);
						break;
						
					//width style color 
					case "border":
						ret = Vector.<String>(pProperty.split(" "));
						_parseBorder(pStyle, "border-width", ret[0]);
						_parseBorder(pStyle, "border-style", ret[1]);
						_parseBorder(pStyle, "border-color", ret[2]);
						break;
						
					//unknown border property, store
					default:
						_setValue(pStyle, pKey, pProperty);
				}
			}
		}

		private function _setValue(pStyle:Object, pProperty:String, pValue:String, pIsImage:Boolean = false):void{
//			trace("---")
//			trace(_toCamelCase(pProperty))
//			trace(pValue)
//			trace("---")
			
			if (isNullOrEmpty(pValue)) return;
			pStyle[_toCamelCase(pProperty)] = StringUtils.trim(pValue);
			
			//already loaded/instantiated?
			if (!pIsImage || _externalBitmaps[pValue]) return;
			
			_externalBitmaps[pValue] = true;
				
			if (pValue.indexOf("@") == 0){//internal bitmap
				try {
					var klass:Class = getClassByName(pValue.substr(1));
					var bmpD:BitmapData = BitmapData(new klass(0, 0));
					_bitmaps[pValue] = bmpD; 
						
				} catch (e:Error){
					throw new ArgumentError("BitmapData [" + pValue + "] has not been included at compile time.", pValue);
				}
				
			} else {//external bitmap
				var qi:DisplayObjectLoader = DisplayObjectLoader(_loader.addLoader(new DisplayObjectLoader(pValue)));
				qi.userData["name"] = pValue;
				qi.onComplete.add(_bitmapLoaded);
				_externals++;
			}
		}

		private function _toCamelCase(pString:String):String {
			if (_hypen.test(pString) ) {
			    if (_propertyCache[pString]) { // already converted
			        return _propertyCache[pString];
			    }
			    
			    var match:Object;
			    while(match = _hypen.exec(pString)) pString = pString.replace(match[1], match[1].substr(1).toUpperCase());
			    _propertyCache[pString] = pString;    
		    }
		    
		    return pString;
		}
	}
}

import extremefx.extensions.isNullOrEmpty;
import extremefx.modules.css.DomNodeKinds;
import extremefx.modules.css.IDomNode;
import flash.utils.Dictionary;






final class Selector {
	private static const OP_DESC:uint = 0;   //no operator
    private static const OP_CHILD:uint = 1;  //operator >
    private static const OP_SIBL:uint = 2;   //operator + 
    
	private var _nodes:Vector.<Node>;
	private var _operators:Vector.<uint>;
	
	public var specificity:int;
	public var order:uint;
	public var contents:Object;
	
	public function Selector(pSelector:String, pContents:Object, pOrder:uint){
		specificity = 0;
		contents = pContents;
		order = pOrder;
		_nodes = new Vector.<Node>();
		_operators = new Vector.<uint>();
		
		var first:Boolean = true;
		var parts:Array = pSelector.split(" ");
		
		for each (var part:String in parts){
			if (part == ">"){
				_operators.push(OP_CHILD);
				
			} else if (part == "+"){
				_operators.push(OP_SIBL);
				
			} else {
				if (first){
					first = false;
					
				} else { 
					_operators.push(OP_DESC);
				}
					
				var node:Node = new Node(part);
				specificity += node.specificity; 
				_nodes.push(node);
			}
		}
	}
	
	public function matches(pNode:IDomNode, pPseudo:String):Boolean {
		var i:int = _nodes.length - 1;
		var node:Node;
		//check the basic node 
		if (-1 < i){
			node = _nodes[i];
			if (!node.matches(pNode, pPseudo)) {
				return false;
			}
			i--;
		}
		
		//check related nodes 
		var rel:IDomNode = pNode;
		while (-1 < i){
			node = _nodes[i];
			switch (_operators[i]){
				case OP_DESC:
					rel = _findAncestor(rel, node); 
					break;
					
				case OP_CHILD: 
					rel = _findParent(rel, node); 
					break;
					
                case OP_SIBL: 
                	rel = _findSibling(rel, node); 
                	break;  
			}
			
			if (rel == null) {
				return false; 
			}
			
			i--;
		}
		return true;
	}
	
	/**
     * Locates an ancestor of this element that corresponds to the specified
     * conditions.
     */ 
	private function _findAncestor(pNode:IDomNode, pSpec:Node):IDomNode {
		var n:IDomNode = pNode.parentNode;
		while (n){
			if (n.nodeKind == DomNodeKinds.ELEMENT && pSpec.matches(n, null)) return n;
			n = n.parentNode;
		}
		
		return null;
	}
	
	/**
     * Locates a parent of this element that corresponds to the specified
     * conditions.
     */ 
	private function _findParent(pNode:IDomNode, pSpec:Node):IDomNode {
		var n:IDomNode = pNode.parentNode;
		while (n){
			if (n.nodeKind == DomNodeKinds.ELEMENT){
				return pSpec.matches(n, null) ? n : null; 
			}
			
			n = n.parentNode;
		}
		
		return null;
	}
	
	/**
     * Locates a preceding sibling of this element that corresponds to the specified
     * conditions.
     */ 
	private function _findSibling(pNode:IDomNode, pSpec:Node):IDomNode {
		var n:IDomNode = pNode.previousSibling;
		while (n){
			if (n.nodeKind == DomNodeKinds.ELEMENT){
				return pSpec.matches(n, null) ? n : null; 
			}
			
			n = n.previousSibling;
		}
		
		return null;
	}
	
	public function toString():String {
		var ret:String = "";
        for (var i:uint = 0; i < _nodes.length; i++) {
            ret += _nodes[i].toString() + " ";
            if (i < _operators.length) {
                var op:uint = _operators[i];
                if (op == OP_CHILD) ret = ret + "> ";
                else if (op == OP_SIBL) ret = ret + "+ ";            
            }
        }
        
        ret += "{\r";
        for (var n:String in contents){
        	ret += "\t"+n+":"+contents[n]+";\r";
        }
        
        ret += "}[" + specificity +":"+order + "]\r\r";
        return ret;
	}
}







final class Node {
	private static const SNAME:uint = 0;     //store characters to element name
    private static const SANAME:uint = 1;    //store characters to attribute name
    private static const SAVALUE:uint = 2;   //store characters to attribute value
    private static const SVOID:uint = 4;     //waiting
    
    public var name:String;
    public var specificity:int;
    
    private var _prioB:int;
    private var _prioC:int;
    private var _attrSpecs:Vector.<Attribute>;

	public function Node(pNode:String) {
		_prioB = _prioC = 0;
		_attrSpecs = new Vector.<Attribute>();
		
		var nme:String = "";
		var aname:String = "";
		var faname:String = "";
		var avalue:String = "";
		
		var dest:uint = SNAME;
		
		var save:Boolean = false;
		var empty:Boolean = true;
		
		var comp:int = 0;
		var fcomp:int = 0;
		
		var inquot:Boolean = false;
		var quote:String = '"';
		
		var index:uint = 0, len:uint = pNode.length;
		
		while (index < len){
			var char:String = pNode.charAt(index);
			
			if (char == "#"){
				faname = "id";
				dest = SAVALUE;
				save = true;
				fcomp = Attribute.COMP_EXACT;
                _prioB++; 
                
			} else if (char == "."){
				faname = "class"; 
				dest = SAVALUE; 
				save = true;
                fcomp = Attribute.COMP_SPACE;
                _prioC++; 
				
			} else if (char == ":"){
				faname = "__pseudoclass"; 
				dest = SAVALUE; 
				save = true;
                fcomp = Attribute.COMP_EXACT;
                _prioC++;
				
			} else if (char == "["){
				faname = ""; 
				dest = SANAME; 
				save = true;
                fcomp = Attribute.COMP_ANY;
                _prioC++; 
				
			} else if (char == "="){
				dest = SVOID;
                comp = Attribute.COMP_EXACT; 
				
			} else if (char == '~' && index+1 < len && pNode.charAt(index+1) == '=') {
				dest = SVOID;
                comp = Attribute.COMP_SPACE;
                index++; 
				
			} else if (char == '|' && index+1 < len && pNode.charAt(index+1) == '=') {
				dest = SVOID;
                comp = Attribute.COMP_HYPHEN;
                index++; 
				
			} else if ((char == '"' || char == '\'') && dest == SVOID) {
				dest = SAVALUE;
                quote = char;
                inquot = true; 
                
			} else if (char == quote && dest == SAVALUE) {
                dest = SVOID;
                inquot = false;
                
            } else if (char == ']') {
            	
            } else if (!inquot && (char == '>' || char == '+' || char == ',' || char == ' ')) {
                break; //end of this part
                
            } else {
                if (save) {
                    if (!empty) _attrSpecs.push(new Attribute(aname, avalue, comp));
                    aname = faname; 
                    faname = ""; 
                    avalue = "";
                    comp = fcomp; 
                    fcomp = -1;
                    save = false; 
                    empty = (aname.length == 0);
                }
                
                switch (dest) {
                    case 0: 
                    	nme = nme + char; 
                    	break;
                    	
                    case 1: 
                    	aname = aname + char; 
                    	empty = false; 
                    	break;
                    	
                    case 2: 
                    	avalue = avalue + char; 
                    	empty = false; 
                    	break;
                }
            }
			
			index++;
		}
		
		name = isNullOrEmpty(nme) ? "*" : nme;
		if (!empty) _attrSpecs.push(new Attribute(aname, avalue, comp));
		specificity = _prioB*100 + _prioC*10 + (name == "*" ? 0 : 1);
	}
	
	/**
     * Returns true if the specified element matches this specification.
     */ 
	public function matches(pNode:IDomNode, pPseudo:String):Boolean {
		var match:Boolean = true;
		
		if (name != "*" && name != pNode.nodeName){
			match = false;
			
		} else {
			for each(var attr:Attribute in _attrSpecs){
				if (!_attrMatch(attr, pNode.attributes, pPseudo)){
					match = false;
					break;
				}
				
			}
		}
		
		return match;
	}
	
	private function _attrMatch(pAttr:Attribute, pAttributes:Dictionary, pPseudo:String):Boolean{
		//special system attributes (pseudoclasses)
		if (pPseudo && pAttr.name == "__pseudoclass"){
			return pAttr.value == pPseudo;
		}
		 
		var val:String = pAttributes[pAttr.name];
		return (val != null && pAttr.matches(val));
	}
	
	public function toString():String {
        var ret:String = name;
        for each(var attr:Attribute in _attrSpecs){
        	 ret = ret + "[" + attr.toString() + "]"; 
        }
        
        return ret;
    }
}

final class Attribute {
	public static const COMP_ANY:uint = 0;
	public static const COMP_EXACT:uint = 1;
	public static const COMP_SPACE:uint = 2;
	public static const COMP_HYPHEN:uint = 3;
	
	public var name:String;
	public var value:String;
	
	private var _comp:uint;
	
	public function Attribute(pName:String, pValue:String = "", pComp:int = 0){
		name = pName;
		value = pValue;
		_comp = pComp;
	}
	
	public function matches(pString:String):Boolean {
		if (_comp == COMP_ANY) return true;
		if (_comp == COMP_EXACT) return pString == value;
		
		var sep:String = " ";
		if (_comp == COMP_HYPHEN) sep = "-";
		
		var pos:int = pString.indexOf(sep);
		if (pos == -1) return false;
		
		return ((pos == 0 || pString.charAt(pos-1) == sep) && (pos + value.length == pString.length || pString.charAt(pos+value.length) == sep)); 
	}
	
	public function toString():String {
        var op:String = "?";
        if (_comp == COMP_ANY) op = "*";
        else if (_comp == COMP_EXACT) op = "=";
        else if (_comp == COMP_SPACE) op = "~=";
        else if (_comp == COMP_HYPHEN) op = "|=";
        return name+" "+op+" "+value;
    } 
}
